home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / smaltalk / manchest.lha / MANCHESTER / manchester / 2.2 / Benchmarks.st next >
Text File  |  1993-07-24  |  41KB  |  1,368 lines

  1. "    NAME        Benchmarks
  2.     AUTHOR        SW@cs.man.ac.uk
  3.     FUNCTION timing analysis and comparison: makes tables, diagrams 
  4.     ST-VERSIONS    2.2
  5.     PREREQUISITES     
  6.     CONFLICTS    
  7.     DISTRIBUTION      world
  8.     VERSION        1.1
  9.     DATE    22 Jan 1989
  10. SUMMARY    Benchmarks
  11.     provides facilities for timing benchmarks and analysing
  12.     the proportion of time spent within specified methods (copes
  13.     with recursive code, unlike spyOn:).  Information is shown in
  14.     a table or diagram.  Facilities are provided to compare different
  15.     versions of similar code. (2.2). SW
  16. "!
  17. View subclass: #AnalysisView
  18.     instanceVariableNames: 'displayOrder '
  19.     classVariableNames: ''
  20.     poolDictionaries: ''
  21.     category: 'Benchmarks'!
  22. AnalysisView comment:
  23. 'An AnalysisView displays a TimingAnalysis graphically.
  24. '!
  25.  
  26.  
  27. !AnalysisView methodsFor: 'displaying'!
  28.  
  29. displayView
  30.     "Display a graphical representation of the analysis data."
  31.  
  32.     | box dist anArray vertUnit horizUnit vertPos height totalTally sym lastArray blockHeight blockIndex nextArray nextHeight text |
  33.     totalTally _ model inject: 0 into: [:total :el | total + el].
  34.     box _ self insetDisplayBox.
  35.     1 to: 9 do: 
  36.         [:y | 
  37.         dist _ box top + (box height * y / 10).
  38.         (Line
  39.             from: box left @ dist
  40.             to: box right @ dist
  41.             withForm: nil) display].
  42.     vertUnit _ window height / totalTally.
  43.     horizUnit _ window width / (displayOrder first size * 5 - 1).
  44.     vertPos _ 0.
  45.     1 to: displayOrder size do: 
  46.         [:displayIndex | 
  47.         anArray _ displayOrder at: displayIndex.
  48.         height _ model at: anArray.
  49.         height = 0
  50.             ifFalse: 
  51.                 [1 to: anArray size do: [:index | (anArray at: index)
  52.                         = #X
  53.                         ifFalse: 
  54.                             [sym _ anArray at: index.
  55.                             (lastArray notNil and: [sym = (lastArray at: index)])
  56.                                 ifFalse: 
  57.                                     [blockHeight _ height.
  58.                                     blockIndex _ displayIndex.
  59.                                     [blockIndex < displayOrder size
  60.                                         and: 
  61.                                             [blockIndex _ blockIndex + 1.
  62.                                             
  63.                                             [nextArray _ displayOrder at: blockIndex.
  64.                                             nextHeight _ model at: nextArray.
  65.                                             nextHeight = 0] whileTrue.
  66.                                             (nextArray at: index)
  67.                                                 = sym]]
  68.                                         whileTrue: [blockHeight _ blockHeight + nextHeight].
  69.                                     (Quadrangle
  70.                                         region: (index - 1 * horizUnit * 5 @ vertPos extent: horizUnit * 4 @ (vertUnit * blockHeight))
  71.                                         borderWidth: 1
  72.                                         borderColor: Form black
  73.                                         insideColor: Form lightGray)
  74.                                         displayOn: Display
  75.                                         transformation: self displayTransformation
  76.                                         clippingBox: box.
  77.                                     text _ sym asDisplayText.
  78.                                     text
  79.                                         displayOn: Display
  80.                                         transformation: self displayTransformation
  81.                                         clippingBox: box
  82.                                         align: text boundingBox center
  83.                                         with: index * 5 - 3 * horizUnit @ (vertPos + (vertUnit * blockHeight / 2))
  84.                                         rule: 12
  85.                                         mask: Form black]]].
  86.                 vertPos _ vertPos + (vertUnit * height).
  87.                 lastArray _ anArray]]! !
  88.  
  89. !AnalysisView methodsFor: 'private'!
  90.  
  91. displayOrder: order
  92.     "Set the order to display info."
  93.  
  94.     displayOrder _ order! !
  95. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  96.  
  97. AnalysisView class
  98.     instanceVariableNames: ''!
  99.  
  100.  
  101. !AnalysisView class methodsFor: 'instance creation'!
  102.  
  103. openOn: results displayOrder: order label: aString
  104.     "Open a view to display the multidimensional array 'results' using
  105.     indices from array 'order'."
  106.  
  107.     | view topView |
  108.     view _ self new model: results.
  109.     view displayOrder: order.
  110.     topView _ StandardSystemView
  111.                 model: self
  112.                 label: aString
  113.                 minimumSize: 100 @ 100.
  114.     topView
  115.         addSubView: view
  116.         in: (0@0 extent: 1@1)
  117.         borderWidth: 1.
  118.     "view window: (0@0 extent: 10@100)."
  119.     topView controller open!
  120.  
  121. openOn: results label: aString
  122.     "Determine a sensible order and display results."
  123.  
  124.     | order subscript bestPosition minDiff diff |
  125.     order _ OrderedCollection new.
  126.     results associationsDo: [:assoc | assoc value > 0
  127.             ifTrue: 
  128.                 [order add: assoc key copy beforeIndex: (self bestPositionOf: assoc key in: order) + 1]].
  129.     ^self
  130.         openOn: results
  131.         displayOrder: order
  132.         label: aString! !
  133.  
  134. !AnalysisView class methodsFor: 'private'!
  135.  
  136. bestPositionOf: subscript in: order
  137.     "Determine the position in which 'subscript' can be inserted in 'order',
  138.     leaving the sum of differences between adjacent subscripts minimum."
  139.  
  140.     | minDiff block diff bestPosition |
  141.     minDiff _ 99999.
  142.     block _ 
  143.             [:index | 
  144.             index = order size
  145.                 ifTrue: [diff _ 0]
  146.                 ifFalse: 
  147.                     [diff _ self diff: subscript and: (order at: index + 1).
  148.                     index = 0 ifFalse: [diff _ diff - (self diff: (order at: index) and: (order at: index + 1))]].
  149.             index = 0 ifFalse: [diff _ diff + (self diff: (order at: index) and: subscript)].
  150.             diff = 0
  151.                 ifTrue: 
  152.                     [block _ nil.
  153.                     ^index].
  154.             diff < minDiff
  155.                 ifTrue: 
  156.                     [minDiff _ diff.
  157.                     bestPosition _ index]].
  158.     1 to: order size do: block.
  159.     block value: 0.
  160.     block _ nil.
  161.     ^bestPosition!
  162.  
  163. diff: sub1 and: sub2
  164.     "Find the difference between two subscipts, i.e. how many symbols in each are different."
  165.  
  166.     | total |
  167.     total _ 0.
  168.     1 to: sub1 size do: [:index | (sub1 at: index) == (sub2 at: index) ifFalse: [total _ total + 1]].
  169.     ^total! !
  170.  
  171. Collection subclass: #MultiDimensionalArray
  172.     instanceVariableNames: 'data dimensions '
  173.     classVariableNames: ''
  174.     poolDictionaries: ''
  175.     category: 'Benchmarks'!
  176. MultiDimensionalArray comment:
  177. 'A general facility for an array with fixed dimensions.  The indices can be any objects; they need not be integers.
  178. '!
  179.  
  180.  
  181. !MultiDimensionalArray methodsFor: 'initialize-release'!
  182.  
  183. subscripts: anArray fill: default
  184.     "Set the subscripts to those in anArray, and fill the array."
  185.  
  186.     dimensions _ anArray size.
  187.     data _ self createFrom: anArray index: 1 fill: default! !
  188.  
  189. !MultiDimensionalArray methodsFor: 'accessing'!
  190.  
  191. at: anArray
  192.     "Get the element determined by subscript 'anArray'."
  193.  
  194.     | r |
  195.     dimensions = anArray size ifFalse: [self error: 'Bad subscript'].
  196.     r _ data.
  197.     anArray do: [:sub | r _ r at: sub].
  198.     ^r!
  199.  
  200. at: anArray incrementBy: num
  201.     "Increment by 'num' the element determined by subscript 'anArray'."
  202.  
  203.     | r |
  204.     dimensions = anArray size ifFalse: [self error: 'Bad subscript'].
  205.     dimensions = 0
  206.         ifTrue: [^data _ data + num]
  207.         ifFalse: 
  208.             [r _ data.
  209.             1 to: anArray size - 1 do: [:index | r _ r at: (anArray at: index)].
  210.             ^r at: anArray last put: (r at: anArray last) + num]!
  211.  
  212. at: anArray put: element
  213.     "Set the element determined by subscript 'anArray'."
  214.  
  215.     | r |
  216.     dimensions = anArray size ifFalse: [self error: 'Bad subscript'].
  217.     dimensions = 0
  218.         ifTrue: [^data _ element]
  219.         ifFalse: 
  220.             [r _ data.
  221.             1 to: anArray size - 1 do: [:index | r _ r at: (anArray at: index)].
  222.             ^r at: anArray last put: element]! !
  223.  
  224. !MultiDimensionalArray methodsFor: 'adding'!
  225.  
  226. add: el
  227.     self shouldNotImplement! !
  228.  
  229. !MultiDimensionalArray methodsFor: 'removing'!
  230.  
  231. remove: el ifAbsent: aBlock
  232.     self shouldNotImplement! !
  233.  
  234. !MultiDimensionalArray methodsFor: 'enumerating'!
  235.  
  236. associationsDo: aBlock
  237.     "Perform aBlock for all (index-element) combinations."
  238.  
  239.     self associationsDo: aBlock data: data sub: (Array new: dimensions) index: 1!
  240.  
  241. do: aBlock
  242.     "Perform aBlock for all elements."
  243.  
  244.     self associationsDo: [:assoc | aBlock value: assoc value]! !
  245.  
  246. !MultiDimensionalArray methodsFor: 'printing'!
  247.  
  248. printOn: aStream 
  249.     "Print the information in the array."
  250.  
  251.     self class printOn: aStream.
  252.     aStream nextPutAll: ' ('.
  253.     self
  254.         associationsDo: 
  255.             [:assoc | 
  256.             assoc printOn: aStream.
  257.             aStream space]
  258.         data: data
  259.         sub: (Array new: dimensions)
  260.         index: 1.
  261.     aStream nextPut: $)! !
  262.  
  263. !MultiDimensionalArray methodsFor: 'private'!
  264.  
  265. associationsDo: aBlock data: info sub: sub index: index 
  266.     index > dimensions
  267.         ifTrue: 
  268.             [aBlock value: (Association key: sub value: info)]
  269.         ifFalse: [info class == Array
  270.                 ifTrue: [1 to: info size do: 
  271.                         [:ind | 
  272.                         sub at: index put: ind.
  273.                         self
  274.                             associationsDo: aBlock
  275.                             data: (info at: ind)
  276.                             sub: sub
  277.                             index: index + 1]]
  278.                 ifFalse: [info
  279.                         associationsDo: 
  280.                             [:assoc | 
  281.                             sub at: index put: assoc key.
  282.                             self
  283.                                 associationsDo: aBlock
  284.                                 data: assoc value
  285.                                 sub: sub
  286.                                 index: index + 1]]]!
  287.  
  288. createFrom: anArray index: anInteger fill: default
  289.     | sub r |
  290.     anArray size < anInteger ifTrue: [^default].
  291.     sub _ anArray at: anInteger.
  292.     r _ sub = (1 to: sub size)
  293.                 ifTrue: [Array new: sub size]
  294.                 ifFalse: [Dictionary new].
  295.     sub do: [:subscript | r at: subscript put: (self createFrom: anArray index: anInteger + 1 fill: default)].
  296.     ^r! !
  297. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  298.  
  299. MultiDimensionalArray class
  300.     instanceVariableNames: ''!
  301.  
  302.  
  303. !MultiDimensionalArray class methodsFor: 'instance creation'!
  304.  
  305. new
  306.     "Create a zero-dimensional array."
  307.  
  308.     ^self new: #()!
  309.  
  310. new: anArray
  311.     "Create a new multidimensional array defined by anArray of subscripts."
  312.  
  313.     "MultiDimensionalArray new: (Array with: (1 to: 3) with: #(a b c))."
  314.  
  315.     ^super new subscripts: anArray fill: nil!
  316.  
  317. new: anArray withAll: element
  318.     "Create a new multidimensional array filled with 'element'."
  319.  
  320.     ^super new subscripts: anArray fill: element! !
  321.  
  322. Model subclass: #Table
  323.     instanceVariableNames: 'format form selection '
  324.     classVariableNames: ''
  325.     poolDictionaries: ''
  326.     category: 'Benchmarks'!
  327. Table comment:
  328. 'A general facility for the graphical display and manipulation of a table of information.  Currently only strings, and numbers in a fixed point format, can be displayed.
  329. '!
  330.  
  331.  
  332. !Table methodsFor: 'opening'!
  333.  
  334. openWithLabel: aString
  335.     "Display the table in a StandardSystemView."
  336.  
  337.     | view topView |
  338.     view _ ScrollableFormView new model: self.
  339.     topView _ StandardSystemView
  340.                 model: self
  341.                 label: aString
  342.                 minimumSize: 100 @ 100.
  343.     topView
  344.         addSubView: view
  345.         in: (0 @ 0 extent: 1 @ 1)
  346.         borderWidth: 1.
  347.     topView controller open! !
  348.  
  349. !Table methodsFor: 'accessing'!
  350.  
  351. boxOf: anInteger
  352.     "Get the rectangle that can be selected on row anInteger.  Override if facility required."
  353.  
  354.     ^0@0 extent: 0@0!
  355.  
  356. data: data 
  357.     "Set the data in the table, displaying it on the form."
  358.  
  359.     | colForms colForm type col charHeight x y item string alignLeft disp newColForm |
  360.     charHeight _ TextStyle default lineGrid.
  361.     colForms _ Array new: format size.
  362.     1 to: format size do: 
  363.         [:index | 
  364.         col _ format at: index.
  365.         type _ col at: 2.
  366.         colForm _ Form extent: 0 @ (charHeight * data size).
  367.         y _ 0.
  368.         data do: 
  369.             [:row | 
  370.             item _ row at: index.
  371.             type = #String
  372.                 ifTrue: 
  373.                     [string _ item.
  374.                     alignLeft _ true]
  375.                 ifFalse: [type = #Decimal
  376.                         ifTrue: 
  377.                             [string _ self stringFrom: item fracDigits: (col at: 3).
  378.                             alignLeft _ false]].
  379.             disp _ DisplayText text: (Text string: string emphasis: col first).
  380.             disp width + 1 > colForm width
  381.                 ifTrue: 
  382.                     [newColForm _ Form extent: disp width + 1 @ colForm height.
  383.                     colForm displayOn: newColForm at: (alignLeft
  384.                             ifTrue: [0]
  385.                             ifFalse: [newColForm width - colForm width])
  386.                             @ 0.
  387.                     colForm _ newColForm].
  388.             disp displayOn: colForm at: (alignLeft
  389.                     ifTrue: [1]
  390.                     ifFalse: [colForm width - disp width])
  391.                     @ y.
  392.             y _ y + charHeight].
  393.         colForms at: index put: colForm].
  394.     form _ Form extent: (colForms inject: -5 into: [:total :colForm | total + colForm width + 5])
  395.                     @ (charHeight * data size).
  396.     x _ 0.
  397.     colForms do: 
  398.         [:colForm | 
  399.         colForm displayOn: form at: x @ 0.
  400.         x _ x + colForm width + 5].
  401.     selection notNil ifTrue: [self invertSelection].
  402.     self changed!
  403.  
  404. form
  405.     "Get the form containing the table."
  406.  
  407.     ^form!
  408.  
  409. format: anArray
  410.     "Set the format, ready for some data."
  411.  
  412.     format _ anArray.
  413.     selection _ nil.
  414.     form _ Form new.
  415.     self changed! !
  416.  
  417. !Table methodsFor: 'selecting'!
  418.  
  419. deselect
  420.     "Deselect selected row, if any."
  421.  
  422.     selection notNil ifTrue: [self invertSelection].
  423.     selection _ nil!
  424.  
  425. down
  426.     "Select the node below the selected one."
  427.  
  428.     (selection + 1) * (TextStyle default lineGrid) < form height ifTrue: [self selectRow: selection + 1]!
  429.  
  430. initialSelection
  431.     "No initial selection."!
  432.  
  433. invertSelection
  434.     "Invert the selected box on the form."
  435.  
  436.     form
  437.         fill: (self boxOf: selection)
  438.         rule: Form reverse
  439.         mask: Form black!
  440.  
  441. isSelection
  442.     "Return true if a node has been selected."
  443.  
  444.     ^selection notNil!
  445.  
  446. left
  447.     "Cannot move left."!
  448.  
  449. right
  450.     "Cannot move right."!
  451.  
  452. select: aPoint hold: aBoolean
  453.     "Try to change the selection."
  454.  
  455.     | newSelection |
  456.     newSelection _ aPoint y // TextStyle default lineGrid.
  457.     ((self boxOf: newSelection)
  458.         containsPoint: aPoint)
  459.         ifFalse: [^false].
  460.     selection = newSelection
  461.         ifTrue: [aBoolean
  462.                 ifFalse: 
  463.                     [self deselect.
  464.                     self changed]]
  465.         ifFalse: [selection isNil & aBoolean ifFalse: [self selectRow: newSelection]].
  466.     ^true!
  467.  
  468. selectRow: newSelection
  469.     "Change the selection."
  470.  
  471.     newSelection = selection
  472.         ifFalse: 
  473.             [self deselect.
  474.             selection _ newSelection.
  475.             self invertSelection.
  476.             self changed: (self boxOf: selection) center]!
  477.  
  478. up
  479.     "Select the node above the selected one."
  480.  
  481.     selection > 0 ifTrue: [self selectRow: selection - 1]! !
  482.  
  483. !Table methodsFor: 'private'!
  484.  
  485. stringFrom: number fracDigits: decPlaces 
  486.     "Convert a number (unsigned) into a string, with appropriate number 
  487.     of decimal places."
  488.  
  489.     | int value frac digit |
  490.     decPlaces = 0 ifTrue: [^(number + (1/2)) truncated printString].
  491.     int _ number truncated.
  492.     value _ number - int * 10.
  493.     frac _ String new: decPlaces.
  494.     1 to: decPlaces do: 
  495.         [:place | 
  496.         digit _ value truncated.
  497.         value _ value - digit * 10.
  498.         (place = decPlaces and: [value >= 5])
  499.             ifTrue: 
  500.                 [digit _ digit + 1.
  501.                 digit > 9 ifTrue: [^self stringFrom: number + (1 / 2 / (10 raisedTo: decPlaces)) fracDigits: decPlaces]].
  502.         frac at: place put: (Character digitValue: digit)].
  503.     ^int printString , '.' , frac! !
  504. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  505.  
  506. Table class
  507.     instanceVariableNames: ''!
  508.  
  509.  
  510. !Table class methodsFor: 'instance creation'!
  511.  
  512. format: anArray data: a2DArray 
  513.     "Create a table with the given format and data."
  514.     "format = #((2 String) (1 Decimal 2)): a string (in font 1) plus a
  515.     number with 2 digits after the decimal point (in font 2).  The data
  516.     must be an array of (string, number) pairs."
  517.  
  518.     | view topView |
  519.     view _ ScrollableFormView new model: ((self new format: anArray)
  520.                     data: a2DArray).
  521.     topView _ StandardSystemView
  522.                 model: self
  523.                 label: 'A table'
  524.                 minimumSize: 100 @ 100.
  525.     topView
  526.         addSubView: view
  527.         in: (0 @ 0 extent: 1 @ 1)
  528.         borderWidth: 1.
  529.     topView controller open
  530.  
  531.     "Table
  532.         format: #((1 String) (2 Decimal 3))
  533.         data: #(('Hi there' 42) ('More text' 5.77777) ('A long line of text' 1))"! !
  534.  
  535. MultiDimensionalArray subclass: #TimingAnalysis
  536.     instanceVariableNames: 'inf groups '
  537.     classVariableNames: 'Count IntervalSize Status '
  538.     poolDictionaries: ''
  539.     category: 'Benchmarks'!
  540. TimingAnalysis comment:
  541. 'This class generates a graphical display of the amount of time spent within categories of methods.  At any one time, the processor may be within several of these categories, due to nesting.  The TimingAnalysis interrupts the processor several times to determine the approximate proportions of time spent within groups of categories.  Unlike the standard profiling tool "MessageTally spyOn:", this class yields useful results with recursive code.
  542.  
  543. An AnalysisView displays this information graphically.  The categories of methods the processor is in at a particular time are shown as a series of blocks horizontally across the view.  Time is shown vertically, but the view does not indicate the order in which events took place.
  544.  
  545. The user must specify the horizontal position of each category in the view.  It is convenient to show mutually exclusive categories at the same position, and lower level categories to the right, as this produces best results.  If two categories at the same position are not mutually exclusive, the higher level one takes priority.
  546.  
  547. An array is used to specify analysis information.  It contains a number of sub-arrays which each correspond to a category.  Each sub-array contains a list of methods, a horizontal position number (from 1 at the left-hand-side), and a category name which is displayed in the view.  The list of methods is itself an array, which contains a number of selectors and/or (class,selector) pairs.
  548.  
  549. For example:
  550.     #(
  551.         ((factorial) 1 factorial)
  552.         (((Integer *) =) 2 arithmetic)
  553.     )
  554. has two categories (factorial to the left of arithmetic).
  555.  
  556. Note that the processor never interrupts primitive methods.
  557.  
  558.  
  559.  
  560. A TimingAnalysis is often used from a BenchmarkSeries; see that class and FactorialBenchmarks for examples.
  561. '!
  562.  
  563.  
  564. !TimingAnalysis methodsFor: 'accessing'!
  565.  
  566. info: info
  567.     "Set the analysis information."
  568.  
  569.     inf _ info.
  570.     groups _ nil! !
  571.  
  572. !TimingAnalysis methodsFor: 'opening'!
  573.  
  574. openWithLabel: label
  575.     "Open an AnalysisView."
  576.  
  577.     groups _ nil.
  578.     AnalysisView openOn: self label: label! !
  579.  
  580. !TimingAnalysis methodsFor: 'analysis'!
  581.  
  582. analyse: aBlock
  583.     "Analyse the evaluation of aBlock, storing results in the array.
  584.     Return an estimate of the time taken."
  585.  
  586.     | myDelay active time timer observedProcess action count analysisContext |
  587.     groups isNil ifTrue: [self computeGroups].
  588.     observedProcess _ Processor activeProcess.
  589.     myDelay _ Delay forMilliseconds: IntervalSize.
  590.     analysisContext _ thisContext.
  591.     active _ true.
  592.     count _ 0.
  593.     timer _ 
  594.             [[active]
  595.                 whileTrue: 
  596.                     [myDelay wait.
  597.                     active
  598.                         ifTrue: 
  599.                             [action _ Array new: dimensions withAll: #X.
  600.                             observedProcess suspendedContext == aBlock
  601.                                 ifFalse: 
  602.                                     [self
  603.                                         analyseContext: observedProcess suspendedContext
  604.                                         in: aBlock
  605.                                         notIn: analysisContext
  606.                                         action: action].
  607.                             count _ count + 1.
  608.                             time _ Time millisecondClockValue.
  609.                             [time = Time millisecondClockValue] whileTrue]].
  610.             nil] newProcess.
  611.     timer priority: Processor userInterruptPriority.
  612.     timer resume.
  613.     aBlock value.
  614.     active _ false.
  615.     analysisContext _ nil.
  616.     ^count * (IntervalSize + 20)! !
  617.  
  618. !TimingAnalysis methodsFor: 'private'!
  619.  
  620. analyseContext: context in: aBlock notIn: analysisContext action: action
  621.     | type |
  622.     context == analysisContext ifTrue: [^self].
  623.     context == aBlock | context isNil ifTrue: [^self at: action incrementBy: 1].
  624.     type _ groups at: context method ifAbsent: [].
  625.     type isNil ifFalse: [action at: type first put: type last].
  626.     self
  627.         analyseContext: context home sender
  628.         in: aBlock
  629.         notIn: analysisContext
  630.         action: action!
  631.  
  632. computeGroups
  633.     "For efficiency, compute a dictionary from compiled methods to 
  634.     (position, name) pairs."
  635.  
  636.     | pair extra class |
  637.     groups _ IdentityDictionary new: 256.
  638.     extra _ Dictionary new.
  639.     inf do: 
  640.         [:item | 
  641.         pair _ Array with: (item at: 2)
  642.                     with: item last.
  643.         item first do: [:selOrMeth | selOrMeth class == Array
  644.                 ifTrue: 
  645.                     [class _ Smalltalk at: selOrMeth first.
  646.                     groups at: (((selOrMeth at: 2) = #class
  647.                             ifTrue: [class class]
  648.                             ifFalse: [class])
  649.                             compiledMethodAt: selOrMeth last asSymbol)
  650.                         put: pair]
  651.                 ifFalse: [extra at: selOrMeth put: pair]]].
  652.     extra isEmpty ifFalse:
  653.         [Smalltalk allBehaviorsDo:
  654.             [:class | extra associationsDo:
  655.                 [:assoc | (class includesSelector: assoc key)
  656.                     ifTrue: [groups at: (class compiledMethodAt: assoc key)
  657.                             put: assoc value]]]]! !
  658. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  659.  
  660. TimingAnalysis class
  661.     instanceVariableNames: ''!
  662.  
  663.  
  664. !TimingAnalysis class methodsFor: 'instance creation'!
  665.  
  666. analyse: aBlock using: analysisInfo
  667.     "Analyse the evaluation of aBlock, displaying the results in a view."
  668.  
  669.     | analysis |
  670.     (analysis _ self info: analysisInfo) analyse: aBlock.
  671.     analysis openWithLabel: 'TimingAnalysis'!
  672.  
  673. info: analysisInfo
  674.     "Set up a structure to store analysis information."
  675.  
  676.     | analysis dims |
  677.     dims _ Array new: (analysisInfo inject: 0 into: [:total :item | total max: (item at: 2)]).
  678.     1 to: dims size do: [:index | dims at: index put: (OrderedCollection with: #X)].
  679.     analysisInfo do: [:item | (dims at: (item at: 2)) add: item last].
  680.     analysis _ super new: dims withAll: 0.
  681.     ^analysis info: analysisInfo! !
  682.  
  683. !TimingAnalysis class methodsFor: 'class initialization'!
  684.  
  685. initialize
  686.     "TimingAnalysis initialize"
  687.  
  688.     IntervalSize _ 20
  689.  
  690.     "Note that the interval size is rather greater in practice, since the processor interrupts less frequently."! !
  691.  
  692. TimingAnalysis initialize!
  693.  
  694.  
  695. Table subclass: #BenchmarkSeries
  696.     instanceVariableNames: 'timings version status analysis '
  697.     classVariableNames: ''
  698.     poolDictionaries: ''
  699.     category: 'Benchmarks'!
  700. BenchmarkSeries comment:
  701. 'This is a benchmarking and profiling tool, designed as an aid to the development of an efficient application.  It can be used to compare execution times of different "versions" of the application, and also to identify code most suitable for optimisation.
  702.  
  703. A series of benchmarks is used to test a specific application.  These benchmarks are defined as methods within the "benchmarks" protocol of a subclass of this class (BenchmarkSeries).  Class FactorialBenchmarks provides an example series of benchmarks.  Each benchmark method is of the form:
  704.         "Initialization"
  705.         self time: ["code to be timed"].
  706.         self check: ["Validating: code to check if result was correct, yielding a boolean"].
  707.         "Finishing"
  708. where "Initialization", "Validating" and "Finishing" are optional pieces of Smalltalk code.  Additionally, methods "initialize" and "finish" may be overridden to perform actions applicable to all benchmarks.
  709.  
  710. This tool can be used to compare different versions of the application.  The benchmarks can of course be timed, the application changed, and the benchmarks timed again.  Alternatively, if different versions of the application are present in the image, their times can be compared.  The instance variable "version" is used for this purpose; it is typically a symbol (or nil if only one version).  Benchmarks test the value of the instance variable and execute code accordingly.
  711.  
  712. An instance of class "BenchmarkSeries" stores execution times for some (or all) of the benchmarks in the series, for a given version.  The "time:for:" method is usually used to create such an instance and display results in a view; see the examples in class FactorialBenchmarks.  Use the left button of the mouse to scroll and select a particular benchmark from the table of results, and the left button to bring up a menu.  The cursor keys can also be used.
  713.  
  714. Various options are provided to customise this benchmark tool for a particular application.  These options are modified by overriding the following methods:
  715.  
  716.     minTime:        Yields the minimum amount of time to be spent performing a benchmark.  The benchmark is timed several times if necessary.  The default is 30 seconds, which usually yields results to an accuracy of at least 10% (depending on amount of paging etc.)
  717.  
  718.     maxTimes        Yields the maximum number of times a benchmark is to be timed (default 500).  Used to avoid spending too long on very quick benchmarks with large initialization overheads.
  719.  
  720.     trace:            Produce some output in the Transcript, by default.
  721.  
  722.     stringFromVersion:        versionFromString:do:
  723.                     A version can be any object; override if anything other than symbols (or nil).
  724.  
  725.     analysisInfo    The information describing how benchmarks are analysed, using the "analyse" option from the menu.  See TimingAnalysis for more information.
  726.  
  727.  
  728.  
  729. See class FactorialBenchmarks.  Happy benchmarking!!
  730.                                                     Steve Wallis (22.2.89)'!
  731.  
  732.  
  733. !BenchmarkSeries methodsFor: 'initialize-release'!
  734.  
  735. finish
  736.     "Perform any action required after any benchmark.  Default is to do nothing."!
  737.  
  738. initialize
  739.     "Perform any initialization required before any benchmark to yield
  740.     accurate timings (such as flushing caches).  Default is to do nothing."! !
  741.  
  742. !BenchmarkSeries methodsFor: 'accessing'!
  743.  
  744. label
  745.     "Get the label for the view."
  746.  
  747.     ^version notNil
  748.         ifTrue: [self class name , ' (' , (self stringFromVersion: version) , ')']
  749.         ifFalse: [self class name]! !
  750.  
  751. !BenchmarkSeries methodsFor: 'opening'!
  752.  
  753. boxOf: anInteger
  754.     "Get the rectangle that can be selected on row anInteger."
  755.  
  756.     | charHeight |
  757.     charHeight _ TextStyle default lineGrid.
  758.     ^0 @ (anInteger * charHeight) extent: (DisplayText text: (Text string: ((timings at: (anInteger + 1))) first emphasis: 2)) width + 1 @ charHeight!
  759.  
  760. open
  761.     "Display the benchmarks in a StandardSystemView."
  762.  
  763.     self format: #((2 String) (1 Decimal 4)) deepCopy.
  764.     self data: timings.
  765.     self openWithLabel: self label! !
  766.  
  767. !BenchmarkSeries methodsFor: 'timing'!
  768.  
  769. analyse: aSymbol
  770.     "Produce a TimingAnalysis of the benchmark denoted by aSymbol."
  771.  
  772.     | times total |
  773.     self trace: 'Analysing ' , aSymbol.
  774.     analysis _ TimingAnalysis info: self analysisInfo.
  775.     self doBenchmark: aSymbol status: #analyse.
  776.     times _ status = 0
  777.                 ifTrue: [self maxTimes]
  778.                 ifFalse: [((self minTime / status + 1) min: self maxTimes) truncated].
  779.     times > 1
  780.         ifTrue: 
  781.             [self trace: ' ' , times printString , ' times'.
  782.             times - 1
  783.                 timesRepeat: 
  784.                     [self trace: '.'.
  785.                     self doBenchmark: aSymbol status: #analyse]].
  786.     self trace: (String with: Character cr).
  787.     analysis openWithLabel: self label , '\TimingAnalysis of ' withCRs , aSymbol!
  788.  
  789. benchmark: aSymbol
  790.     "Time a benchmark denoted by aSymbol (which must be a valid
  791.     method).  Add the time taken to the collection of timings."
  792.  
  793.     self benchmark: aSymbol insertAt: timings size + 1!
  794.  
  795. benchmark: aSymbol insertAt: index
  796.     "Time a benchmark denoted by aSymbol (which must be a valid
  797.     method).  Record the time taken in the collection of timings."
  798.  
  799.     | times total |
  800.     self trace: 'Performing ' , aSymbol.
  801.     self doBenchmark: aSymbol status: #time.
  802.     times _ status = 0
  803.                 ifTrue: [self maxTimes]
  804.                 ifFalse: [((self minTime / status + 1) min: self maxTimes) truncated].
  805.     times > 1
  806.         ifTrue: 
  807.             [self trace: ' ' , times printString , ' times'.
  808.             total _ status.
  809.             times - 1
  810.                 timesRepeat: 
  811.                     [self trace: '.'.
  812.                     self doBenchmark: aSymbol status: #time.
  813.                     total _ total + status].
  814.             status _ total / times].
  815.     self trace: (String with: Character cr).
  816.     timings add: (Array with: aSymbol with: status / 1000) beforeIndex: index!
  817.  
  818. time: aBlock
  819.     "Evaluate aBlock recording the execution time in timeTaken.  All
  820.     benchmarks should call this method once."
  821.  
  822.     status == #time ifTrue: [^status _ Time millisecondsToRun: aBlock].
  823.     status == #analyse ifTrue: [^status _ analysis analyse: aBlock].
  824.     self error: 'Bad benchmark'! !
  825.  
  826. !BenchmarkSeries methodsFor: 'validating'!
  827.  
  828. check: aBlock
  829.     "Evaluate aBlock, issue an error message if it does not yield true."
  830.  
  831.     aBlock value == true ifFalse: [self error: 'Benchmark yields incorrect result']! !
  832.  
  833. !BenchmarkSeries methodsFor: 'options'!
  834.  
  835. analysisInfo
  836.     "When analysing (using menu), identify categories of methods using this structure."
  837.  
  838.     self subclassResponsibility!
  839.  
  840. maxTimes
  841.     "Get the maximum number of times a benchmark may need to be
  842.     performed to get an accurate value.  Used in conjunction with
  843.     minTime."
  844.  
  845.     ^500!
  846.  
  847. minTime
  848.     "Get the minimum amount of time (in milliseconds) that should be spent
  849.     performing a benchmark.  Used in conjunction with maxTimes."
  850.  
  851.     ^30000!
  852.  
  853. stringFromVersion: aVersion
  854.     "Convert aVersion into a string."
  855.  
  856.     ^(aVersion isKindOf: String)
  857.         ifTrue: [aVersion]
  858.         ifFalse: [aVersion printString]!
  859.  
  860. trace: aString
  861.     "Default tracing displays aString on the transcript."
  862.  
  863.     Transcript show: aString!
  864.  
  865. versionFromString: aString do: aBlock
  866.     "Convert aString into a version, and perform aBlock if legal.  By
  867.     default, convert strings into symbols and ignore if null string."
  868.  
  869.     aString isEmpty ifFalse: [aBlock value: aString asSymbol]! !
  870.  
  871. !BenchmarkSeries methodsFor: 'menu messages'!
  872.  
  873. add
  874.     "Perform a benchmark, and add to table."
  875.  
  876.     | symbols string num |
  877.     symbols _ self class organization listAtCategoryNamed: #benchmarks.
  878.     string _ symbols inject: '' into: [:total :symbol | total , (symbol , (String with: Character cr))].
  879.     num _ (PopUpMenu labels: (string copyFrom: 1 to: string size - 1)) startUp.
  880.     num = 0
  881.         ifFalse: 
  882.             [symbol _ symbols at: num.
  883.             1 to: timings size do: [:index | (timings at: index) first = symbol
  884.                     ifTrue: 
  885.                         [timings removeAtIndex: index.
  886.                         self benchmark: symbol insertAt: index.
  887.                         selection _ index - 1.
  888.                         ^self data: timings]].
  889.             selection isNil
  890.                 ifTrue: 
  891.                     [selection _ timings size.
  892.                     self benchmark: symbol]
  893.                 ifFalse: [self benchmark: symbol insertAt: selection + 1].
  894.             self data: timings]!
  895.  
  896. analyse
  897.     "Produce a TimingAnalysis of the selected benchmark."
  898.  
  899.     self analyse: (timings at: selection + 1) first!
  900.  
  901. noSelectionMenu
  902.     ^CachedActionMenu
  903.         labels: 'add\spawn\set precision' withCRs
  904.         lines: #(1 2)
  905.         selectors: #(add spawn setPrecision)!
  906.  
  907. remove
  908.     "Remove the selected benchmark."
  909.  
  910.     timings removeAtIndex: selection + 1.
  911.     selection _ nil.
  912.     self data: timings!
  913.  
  914. selectionMenu
  915.     ^CachedActionMenu
  916.         labels: 'add\remove\spawn\analyse\set precision' withCRs
  917.         lines: #(2 4)
  918.         selectors: #(add remove spawn analyse setPrecision)!
  919.  
  920. setPrecision
  921.     "Ask the user to specify the number of decimal places."
  922.  
  923.     | oldPrecision text stream int |
  924.     oldPrecision _ format last last.
  925.     text _ FillInTheBlank request: 'Enter number of decimal places' initialAnswer: oldPrecision printString.
  926.     text isEmpty
  927.         ifFalse: 
  928.             [stream _ ReadStream on: text.
  929.             int _ Integer readFrom: stream.
  930.             stream atEnd & (int >= 0) ifFalse: [self error: 'Positive integer required'].
  931.             oldPrecision = int
  932.                 ifFalse: 
  933.                     [format last at: 3 put: int.
  934.                     self data: timings]]!
  935.  
  936. spawn
  937.     "Produce another BenchmarkSeries with identical benchmarks."
  938.  
  939.     | initial string |
  940.     initial _ version isNil
  941.                 ifTrue: ['']
  942.                 ifFalse: [self stringFromVersion: version].
  943.     string _ FillInTheBlank request: 'Enter version' initialAnswer: initial.
  944.     string = initial
  945.         ifTrue: [self class time: (timings collect: [:pair | pair first])
  946.                 for: version]
  947.         ifFalse: [self versionFromString: string do: [:newVersion | self class time: (timings collect: [:pair | pair first])
  948.                     for: newVersion]]! !
  949.  
  950. !BenchmarkSeries methodsFor: 'private'!
  951.  
  952. doBenchmark: aSymbol status: stat
  953.  
  954.     self initialize.
  955.     status _ stat.
  956.     self perform: aSymbol.
  957.     self finish.
  958.     status == stat ifTrue: [self error: 'Bad benchmark'].!
  959.  
  960. version: any
  961.  
  962.     version _ any.
  963.     timings _ OrderedCollection new! !
  964. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  965.  
  966. BenchmarkSeries class
  967.     instanceVariableNames: ''!
  968.  
  969.  
  970. !BenchmarkSeries class methodsFor: 'instance creation'!
  971.  
  972. new
  973.     "Create a structure to store benchmark results."
  974.  
  975.     ^self version: nil!
  976.  
  977. version: aVersion
  978.     "Create a structure to store benchmark results using aVersion."
  979.  
  980.     ^super new version: aVersion! !
  981.  
  982. !BenchmarkSeries class methodsFor: 'timing'!
  983.  
  984. time: aCollection
  985.     "Time all the benchmarks in aCollection."
  986.  
  987.     ^self time: aCollection for: nil!
  988.  
  989. time: aCollection for: version
  990.     "Time all the benchmarks in aCollection, and display the timings in a table."
  991.  
  992.     | bench |
  993.     Smalltalk garbageCollect.
  994.     bench _ self version: version.
  995.     aCollection do: [:symbol | bench benchmark: symbol].
  996.     bench open! !
  997.  
  998.  
  999. BenchmarkSeries subclass: #FactorialBenchmarks
  1000.     instanceVariableNames: ''
  1001.     classVariableNames: 'Cache '
  1002.     poolDictionaries: ''
  1003.     category: 'Benchmarks'!
  1004. FactorialBenchmarks comment:
  1005. 'This class provides three benchmarks to test three versions of "factorial".  Spot the difference between "cached2" and "cached" (the more efficient "cached2" was developed with the help of this benchmarking tool!!)
  1006.  
  1007. See the examples in this class.
  1008. '!
  1009.  
  1010.  
  1011. !FactorialBenchmarks methodsFor: 'initialize-release'!
  1012.  
  1013. initialize
  1014.     "Initialize the cache, if necessary."
  1015.  
  1016.     version == #normal ifFalse: [Cache _ OrderedCollection with: 1]! !
  1017.  
  1018. !FactorialBenchmarks methodsFor: 'benchmarks'!
  1019.  
  1020. fac10
  1021.     "Benchmark to time 10!!."
  1022.  
  1023.     | fac |
  1024.     self time: [fac _ self doFactorial: 10].
  1025.     self check: [fac = '3628800']!
  1026.  
  1027. fac100
  1028.     "Benchmark to time 100!!."
  1029.  
  1030.     | fac |
  1031.     self time: [fac _ self doFactorial: 100].
  1032.     self check: [fac = '93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000']!
  1033.  
  1034. facs40
  1035.     "Benchmark to time computation of factorials 0 to 40."
  1036.  
  1037.     self time: [0 to: 40 do: [:int | self doFactorial: int]]! !
  1038.  
  1039. !FactorialBenchmarks methodsFor: 'factorials'!
  1040.  
  1041. cachedFactorial2: anInteger
  1042.     "Compute the factorial of anInteger, using the cache."
  1043.  
  1044.     | result |
  1045.     ^anInteger > Cache size
  1046.         ifTrue: 
  1047.             [result _ Cache last.
  1048.             Cache size + 1 to: anInteger do: 
  1049.                 [:n | 
  1050.                 result _ n * result.
  1051.                 Cache addLast: result].
  1052.             result]
  1053.         ifFalse: [anInteger > 0
  1054.                 ifTrue: [Cache at: anInteger]
  1055.                 ifFalse: [anInteger = 0
  1056.                         ifTrue: [1]
  1057.                         ifFalse: [self error: 'Bad factorial']]]!
  1058.  
  1059. cachedFactorial: anInteger
  1060.     "Compute the factorial of anInteger, using the cache."
  1061.  
  1062.     | result |
  1063.     ^anInteger > Cache size
  1064.         ifTrue: 
  1065.             [result _ Cache last.
  1066.             Cache size + 1 to: anInteger do: 
  1067.                 [:n | 
  1068.                 result _ result * n.
  1069.                 Cache addLast: result].
  1070.             result]
  1071.         ifFalse: [anInteger > 0
  1072.                 ifTrue: [Cache at: anInteger]
  1073.                 ifFalse: [anInteger = 0
  1074.                         ifTrue: [1]
  1075.                         ifFalse: [self error: 'Bad factorial']]]!
  1076.  
  1077. doFactorial: anInteger
  1078.     "Compute the factorial of anInteger and convert to a string."
  1079.  
  1080.     ^(version == #normal
  1081.         ifTrue: [anInteger factorial]
  1082.         ifFalse: [version == #cached
  1083.                 ifTrue: [self cachedFactorial: anInteger]
  1084.                 ifFalse: [self cachedFactorial2: anInteger]]) printString! !
  1085.  
  1086. !FactorialBenchmarks methodsFor: 'options'!
  1087.  
  1088. analysisInfo
  1089.     "When analysing (using menu), identify categories of methods using this structure."
  1090.  
  1091.     ^#(
  1092.         ((printString) 1 printString)
  1093.         ((factorial cachedFactorial: cachedFactorial2:) 1 factorial)
  1094.         ((*) 2 multiply)
  1095.         )! !
  1096. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1097.  
  1098. FactorialBenchmarks class
  1099.     instanceVariableNames: ''!
  1100.  
  1101.  
  1102. !FactorialBenchmarks class methodsFor: 'examples'!
  1103.  
  1104. exampleWorkspace
  1105.     "Select the following, and evaluate using doIt."
  1106.  
  1107. FactorialBenchmarks time: #(fac10 fac100 facs40) for: #normal.
  1108.  
  1109. FactorialBenchmarks time: #(fac10 fac100 facs40) for: #cached.
  1110.  
  1111. FactorialBenchmarks time: #(fac10 fac100 facs40) for: #cached2.! !
  1112.  
  1113. "****"
  1114.  
  1115. ActionMenu subclass: #CachedActionMenu
  1116.     instanceVariableNames: ''
  1117.     classVariableNames: 'Cache '
  1118.     poolDictionaries: ''
  1119.     category: 'Interface-Menus'!
  1120.  
  1121. "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
  1122.  
  1123. CachedActionMenu class
  1124.     instanceVariableNames: ''!
  1125.  
  1126.  
  1127. !CachedActionMenu class methodsFor: 'class initialization'!
  1128.  
  1129. initialize
  1130.     "Clear cache."
  1131.  
  1132.     "CachedActionMenu initialize"
  1133.  
  1134.     Cache _ Dictionary new! !
  1135.  
  1136. !CachedActionMenu class methodsFor: 'instance creation'!
  1137.  
  1138. labels: aString lines: anArray selectors: selArray 
  1139.     "Answer a menu with lables from aString lines form anArray and 
  1140.     selectors from selArray."
  1141.  
  1142.     | triple |
  1143.     triple _ Array with: aString with: anArray with: selArray.
  1144.     ^Cache at: triple ifAbsent:
  1145.         [^Cache at: triple put: (super labels: aString lines: anArray selectors: selArray)]! !
  1146.  
  1147. CachedActionMenu initialize!
  1148.  
  1149. "****"
  1150.  
  1151. MouseMenuController subclass: #ScrollableFormController
  1152.     instanceVariableNames: ''
  1153.     classVariableNames: ''
  1154.     poolDictionaries: ''
  1155.     category: 'Benchmarks'!
  1156. ScrollableFormController comment:
  1157. 'A ScrollableFormController provides:
  1158.     a) the facility to scroll the form in the view (using the red button).
  1159.     b) the capability to select items in the view (also using the red button), move about selections (using the cursor keys), and bring up a menu (using the yellow button).'!
  1160.  
  1161.  
  1162. !ScrollableFormController methodsFor: 'control defaults'!
  1163.  
  1164. controlActivity
  1165.     "Use the cursor keys to move through the form."
  1166.  
  1167.     | key |
  1168.     sensor keyboardPressed
  1169.         ifTrue: 
  1170.             [key _ sensor keyboard asciiValue.
  1171.             model isSelection
  1172.                 ifTrue: 
  1173.                     [key = 185 ifTrue: [model left].
  1174.                     key = 187 ifTrue: [model right].
  1175.                     key = 183 ifTrue: [model up].
  1176.                     key = 189 ifTrue: [model down]]].
  1177.     super controlActivity!
  1178.  
  1179. isControlActive
  1180.     ^super isControlActive & sensor blueButtonPressed not! !
  1181.  
  1182. !ScrollableFormController methodsFor: 'menu messages'!
  1183.  
  1184. menuMessageReceiver
  1185.     ^model!
  1186.  
  1187. redButtonActivity
  1188.     "Use the red button to scroll and select items in the form."
  1189.  
  1190.     | cursorPoint delta formPoint |
  1191.     formPoint _ view formPointAt: sensor cursorPoint.
  1192.     (formPoint notNil and: [model select: formPoint hold: false])
  1193.         ifTrue: [[sensor redButtonPressed]
  1194.                 whileTrue: 
  1195.                     [formPoint _ view formPointAt: sensor cursorPoint.
  1196.                     formPoint notNil ifTrue: [model select: formPoint hold: true]]]
  1197.         ifFalse: [Cursor crossHair
  1198.                 showWhile: 
  1199.                     [cursorPoint _ sensor cursorPoint.
  1200.                     [sensor redButtonPressed and: [self isControlActive]]
  1201.                         whileTrue: 
  1202.                             [[(sensor cursorPoint - cursorPoint) abs < (1 @ 1) and: [sensor redButtonPressed]] whileTrue.
  1203.                             delta _ cursorPoint.
  1204.                             delta _ (cursorPoint _ sensor cursorPoint) - delta.
  1205.                             view scrollBy: delta]]]!
  1206.  
  1207. yellowButtonActivity
  1208.     "Bring up a yellow button menu."
  1209.  
  1210.     | index menu |
  1211.     menu _ model isSelection
  1212.                 ifTrue: [model selectionMenu]
  1213.                 ifFalse: [model noSelectionMenu].
  1214.     menu isNil
  1215.         ifTrue: 
  1216.             [view flash.
  1217.             super controlActivity]
  1218.         ifFalse: 
  1219.             [index _ menu startUpYellowButton.
  1220.             index ~= 0
  1221.                 ifTrue: 
  1222.                     [self controlTerminate.
  1223.                     model perform: (menu selectorAt: index).
  1224.                     self controlInitialize]]! !
  1225.  
  1226. "****"
  1227.  
  1228. View subclass: #ScrollableFormView
  1229.     instanceVariableNames: 'offset formBox '
  1230.     classVariableNames: ''
  1231.     poolDictionaries: ''
  1232.     category: 'Benchmarks'!
  1233. ScrollableFormView comment:
  1234. 'A ScrollableFormView displays a form or part of a form. Its instance variable ''offset'' contains the position of the form relative to a central position in the view.'!
  1235.  
  1236.  
  1237. !ScrollableFormView methodsFor: 'initialize-release'!
  1238.  
  1239. initialize
  1240.     "Reset offset."
  1241.  
  1242.     offset _ 0@0.
  1243.     super initialize! !
  1244.  
  1245. !ScrollableFormView methodsFor: 'accessing'!
  1246.  
  1247. formPointAt: screenPoint 
  1248.     "Convert a screen point to a point on the form, or nil if lies outside."
  1249.  
  1250.     | form point |
  1251.     form _ model form.
  1252.     point _ screenPoint - self insetDisplayBox center + form relativeRectangle center - offset.
  1253.     (form relativeRectangle containsPoint: point)
  1254.         ifTrue: [^point]
  1255.         ifFalse: [^nil]!
  1256.  
  1257. offset
  1258.     "Get the offset, i.e. how far off the centre the form is."
  1259.  
  1260.     ^offset!
  1261.  
  1262. offset: aPoint 
  1263.     "Set the offset, i.e. how far off the centre the form is. Forces the 
  1264.     form to lie on or just off the window."
  1265.  
  1266.     | form x y xMax yMax |
  1267.     form _ model form.
  1268.     xMax _ self insetDisplayBox width + form width / 2.
  1269.     yMax _ self insetDisplayBox height + form height / 2.
  1270.     x _ (aPoint x max: xMax negated)
  1271.                 min: xMax.
  1272.     y _ (aPoint y max: yMax negated)
  1273.                 min: yMax.
  1274.     ^offset _ x @ y! !
  1275.  
  1276. !ScrollableFormView methodsFor: 'scrolling'!
  1277.  
  1278. scrollBy: aPoint
  1279.     "Scroll the view."
  1280.  
  1281.     self offset: offset + aPoint.
  1282.     self display!
  1283.  
  1284. scrollToShow: aFormPoint 
  1285.     "Scroll the view if necessary to make aFormPoint clearly in the view."
  1286.  
  1287.     | xMax yMax distFromCentre |
  1288.     distFromCentre _ aFormPoint - model form relativeRectangle center + offset.
  1289.     xMax _ self insetDisplayBox width / 3.
  1290.     yMax _ self insetDisplayBox height / 3.
  1291.     distFromCentre x > xMax ifTrue: [offset x: offset x - distFromCentre x + xMax].
  1292.     distFromCentre x < xMax negated ifTrue: [offset x: offset x - distFromCentre x - xMax].
  1293.     distFromCentre y > yMax ifTrue: [offset y: offset y - distFromCentre y + yMax].
  1294.     distFromCentre y < yMax negated ifTrue: [offset y: offset y - distFromCentre y - yMax].
  1295.     self displaySafe: [self displayView]! !
  1296.  
  1297. !ScrollableFormView methodsFor: 'displaying'!
  1298.  
  1299. displaySafe: aBlock 
  1300.     "Better displaySafe: !!"
  1301.  
  1302.     ((ScheduledControllers isScheduled: self topView controller)
  1303.         and: [self topView isCollapsed not])
  1304.         ifTrue: [super displaySafe: aBlock]!
  1305.  
  1306. displayView
  1307.     "Display the part of the form specified by the offset."
  1308.  
  1309.     | form box formPos displayBox |
  1310.     formBox isNil
  1311.         ifTrue: 
  1312.             [formBox _ 0.
  1313.             model initialSelection].
  1314.     form _ model form.
  1315.     box _ self insetDisplayBox.
  1316.     formPos _ box center - form relativeRectangle center + offset.
  1317.     displayBox _ box intersect: (formPos extent: form extent).
  1318.     formBox = displayBox
  1319.         ifFalse: 
  1320.             [formBox _ displayBox.
  1321.             self clearInside: Form white].
  1322.     form
  1323.         displayOn: Display
  1324.         at: formPos
  1325.         clippingBox: box!
  1326.  
  1327. update: aPoint
  1328.     "Redisplay the view (safely!!)"
  1329.  
  1330.     aPoint class == Point
  1331.         ifTrue: [self scrollToShow: aPoint]
  1332.         ifFalse: [self displaySafe: [self displayView]]! !
  1333.  
  1334. !ScrollableFormView methodsFor: 'controller access'!
  1335.  
  1336. defaultControllerClass
  1337.     ^ScrollableFormController! !
  1338.  
  1339. !Quadrangle methodsFor: 'displaying'!
  1340.  
  1341. displayOn: aDisplayMedium transformation: aWindowingTransformation clippingBox: aRectangle 
  1342.     "Display the border and region of the reciever so that it is scaled and translated
  1343.     with respect to aWindowingTransformation.  The displayed information should
  1344.     be clipped so that only information with the area determined by aRectangle
  1345.     is displayed."
  1346.  
  1347.     | screenRectangle |
  1348.     screenRectangle _ 
  1349.         (aWindowingTransformation applyTo: self) rounded intersect: aRectangle.
  1350.     borderWidth ~~ 0 & (insideColor ~~ nil)
  1351.         ifTrue: 
  1352.             [aDisplayMedium fill: screenRectangle mask: borderColor.
  1353.             aDisplayMedium
  1354.                 fill: (screenRectangle insetBy: borderWidth)
  1355.                 mask: insideColor]! !
  1356.  
  1357. !Quadrangle methodsFor: 'truncation and round off'!
  1358.  
  1359. rounded
  1360.     "Answer a Quadrangle whose rectangle is rounded."
  1361.  
  1362.     ^Quadrangle
  1363.         region: (super rounded)
  1364.         borderWidth: borderWidth
  1365.         borderColor: borderColor
  1366.         insideColor: insideColor! !
  1367.  
  1368.